package com.atlassian.license.decoder;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.Properties;
import java.util.StringTokenizer;

import com.atlassian.extras.api.Product;
import com.atlassian.extras.common.DateEditor;
import com.atlassian.extras.common.LicenseTypeAndEditionResolver;
import com.atlassian.extras.common.log.Logger;
import com.atlassian.extras.common.util.ProductLicenseProperties;
import com.atlassian.extras.decoder.v2.Version2LicenseDecoder;
import com.atlassian.extras.legacy.util.OldLicenseTypeResolver;
import com.atlassian.license.DefaultLicense;
import com.atlassian.license.License;
import com.atlassian.license.LicenseException;
import com.atlassian.license.LicenseManager;
import com.atlassian.license.LicensePair;
import com.atlassian.license.LicenseType;
import com.atlassian.license.LicenseUtils;
import com.atlassian.license.util.StringUtils;

public class LicenseDecoder {
	private static final Logger.Log log = Logger
			.getInstance(LicenseDecoder.class);
	public static final String DURATION_PREFIX = "Duration:";
	public static final String JIRA_APPLICATION_NAME = "JIRA";
	public static final String CONF_APPLICATION_NAME = "CONFLUENCE";

	public static License getLicense(LicensePair pair, String applicationName) {
		try {
			return loadLicense(pair, getPublicKey(applicationName),
					applicationName);
		} catch (LicenseException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	public static boolean isValid(LicensePair pair, String applicationName) {
		return getLicense(pair, applicationName) != null;
	}

	private static PublicKey getPublicKey(String applicationName)
			throws LicenseException {
		String publicKeyFileName = getPublicKeyFilename(applicationName);

		if ((publicKeyFileName == null) || ("".equals(publicKeyFileName))) {
			throw new LicenseException(
					"The filename for the public key is null. This must be set before a public key can be located.");
		}
		try {
			return loadPublicKeyFromFile(publicKeyFileName);
		} catch (Exception e) {
			log.error("Exception looking up public key: " + e.getMessage(), e);
		}
		throw new LicenseException(
				"Exception getting verification from file - possible classloader problem, or corrupt JIRA installation ");
	}

	public static PublicKey loadPublicKeyFromFile(String publicKeyFileName)
			throws IOException, NoSuchAlgorithmException,
			InvalidKeySpecException {
		InputStream keyfis = null;
		ClassLoader contextLoader = Thread.currentThread()
				.getContextClassLoader();

		if (contextLoader != null) {
			keyfis = contextLoader.getResourceAsStream(publicKeyFileName);
		}

		if (keyfis == null) {
			keyfis = LicenseDecoder.class.getClassLoader().getResourceAsStream(
					publicKeyFileName);
		}

		byte[] encKey = LicenseUtils.readKey(keyfis);
		keyfis.close();

		X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encKey);
		KeyFactory keyFactory = KeyFactory.getInstance("DSA");

		return keyFactory.generatePublic(pubKeySpec);
	}

	private static License loadLicense(LicensePair pair, PublicKey publicKey,
			String applicationName) {
		try {
			if (pair.isNG()) {
				return parseNewLicense(pair, applicationName);
			}

			return parseOldLicense(pair, publicKey, applicationName);
		} catch (Exception e) {
			log.error(e);
		}
		return null;
	}

	public static License parseOldLicense(LicensePair pair,
			PublicKey publicKey, String applicationName)
			throws NoSuchAlgorithmException, InvalidKeyException,
			SignatureException, LicenseException {
		Signature signature = Signature.getInstance("SHA1withDSA");
		signature.initVerify(publicKey);
		signature.update(pair.getLicense());

		if (signature.verify(pair.getHash())) {
			String messageString = getDecodedMessage(pair.getLicense());

			StringTokenizer tokenizer = new StringTokenizer(messageString, "^");
			if (tokenizer.hasMoreTokens()) {
				int licenseTypeCode = Integer.parseInt(tokenizer.nextToken());
				LicenseType licenseType = LicenseManager.getInstance()
						.getLicenseType(applicationName, licenseTypeCode);
				Date dateCreated = DateEditor.getDate(tokenizer.nextToken());
				Date datePurchased = DateEditor.getDate(tokenizer.nextToken());
				Date dateExpires = null;
				if (licenseType.expires()) {
					dateExpires = DateEditor.getDate(tokenizer.nextToken());
				}

				String organisation = tokenizer.nextToken();
				String licenseId = getLicenseIdFromLicenseString(pair
						.getOriginalLicenseString());
				int users = -1;
				int clusterCount = 0;

				if (licenseType.requiresUserLimit()) {
					String usersAndClustersLimits = tokenizer.nextToken();
					String[] parts = usersAndClustersLimits.split("\\|");
					if ((parts.length != 1) && (parts.length != 2)) {
						throw new LicenseException(
								"License contained invalid user limit:"
										+ usersAndClustersLimits);
					}
					users = Integer.parseInt(parts[0]);
					if (parts.length == 2) {
						clusterCount = Integer.parseInt(parts[1]);
					}
				}

				String partnerName = null;
				if (tokenizer.hasMoreTokens()) {
					partnerName = tokenizer.nextToken();
				}
				return new DefaultLicense(dateCreated, datePurchased,
						dateExpires, organisation, licenseType, users,
						partnerName, licenseId, clusterCount, null);
			}
			return null;
		}

		log.warn("Signature did not verify properly.");
		return null;
	}

	private static License parseNewLicense(LicensePair pair,
			String applicationName) throws LicenseException {
		Version2LicenseDecoder licenseDecoder = new Version2LicenseDecoder();
		if (!licenseDecoder.canDecode(pair.getOriginalLicenseString())) {
			throw new LicenseException("Failed to decode as V2 license:\n"
					+ pair);
		}
		Properties prop = licenseDecoder
				.decode(pair.getOriginalLicenseString());
		
		prop.setProperty("Evaluation", "false");
		prop.setProperty("LicenseExpiryDate", "2099-12-31");
//		prop.remove("LicenseExpiryDate");
		prop.setProperty("Description", "Confluence: Enterprise");

		Product product = lookupProduct(applicationName);
		ProductLicenseProperties productProperties = new ProductLicenseProperties(
				product, prop);

		String editionName = productProperties.getProperty("LicenseEdition");

		String licenseTypeString = productProperties.getProperty("LicenseType");
		LicenseType licenseType;
		if (licenseTypeString != null) {
			licenseType = LicenseManager.getInstance().getLicenseType(
					applicationName, licenseTypeString);
		} else {
			licenseType = OldLicenseTypeResolver
					.getLicenseType(
							product,
							productProperties.getProperty("LicenseTypeName"),
							Boolean.valueOf(
									productProperties.getProperty("Evaluation"))
									.booleanValue(),
							editionName == null ? null
									: LicenseTypeAndEditionResolver
											.getLicenseEdition(editionName));
		}

		return new LicenseAdaptor(productProperties, licenseType);
	}

	private static Product lookupProduct(String key) {
		for (Product product : Product.values()) {
			if ((product.getName().equalsIgnoreCase(key))
					|| (product.getNamespace().equalsIgnoreCase(key.replace(
							' ', '_')))) {
				return product;
			}
		}
		throw new IllegalArgumentException("Could not find product for key <"
				+ key + ">");
	}

	private static String getLicenseIdFromLicenseString(
			String originalLicenseString) {
		if ((originalLicenseString != null)
				&& (originalLicenseString.length() > 70)) {
			return StringUtils
					.replaceAll(
							StringUtils.replaceAll(StringUtils.replaceAll(
									originalLicenseString, "\n", ""), "\r", ""),
							"\t", "").substring(59, 69).toUpperCase();
		}

		return "";
	}

	private static String getDecodedMessage(byte[] message) {
		try {
			return new String(message, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}

	public static boolean isValid(LicensePair pair, PublicKey publicKey,
			String applicationName) {
		return loadLicense(pair, publicKey, applicationName) != null;
	}

	private static String getPublicKeyFilename(String applicationName) {
		return LicenseManager.getInstance()
				.getLicenseTypeStore(applicationName).getPublicKeyFileName();
	}
}